/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cm_bsp.cpp - BSP format loading
//


#include "cm_local.h"


/*
 ==================
 CM_LoadEntityString
 ==================
*/
static void CM_LoadEntityString (const byte *data, const bspLump_t *lump){

	cm.numEntityChars = lump->length;
	if (cm.numEntityChars < 1)
		Com_Error(false, "CM_LoadMap: no entities (%s)", cm.name);
	else if (cm.numEntityChars > BSP_MAX_ENTITIES)
		Com_Error(false, "CM_LoadMap: too entities (%s)", cm.name);

	cm.entityString = (char *)Mem_Alloc(cm.numEntityChars + 1, TAG_COLLISION);
	cm.size += cm.numEntityChars * sizeof(char);

	Mem_Copy(cm.entityString, data + lump->offset, cm.numEntityChars);
	cm.entityString[cm.numEntityChars] = 0;
}

/*
 ==================
 CM_LoadPVS
 ==================
*/
static void CM_LoadPVS (const byte *data, const bspLump_t *lump){

	cm.noPVS = (byte *)Mem_Alloc(255, TAG_COLLISION);
	cm.size += 255;

	Mem_Fill(cm.noPVS, 0xFF, 255);

	if (!lump->length){
		cm.pvs = NULL;
		return;
	}

	if (lump->length > BSP_MAX_PVS)
		Com_Error(false, "CM_LoadMap: too large PVS (%s)", cm.name);

	cm.pvs = (byte *)Mem_Alloc(lump->length, TAG_COLLISION);
	cm.size += lump->length;

	Mem_Copy(cm.pvs, data + lump->offset, lump->length);
}

/*
 ==================
 CM_LoadPlanes

 NOTE: we do not load the plane type, is it required?
 ==================
*/
static void CM_LoadPlanes (const byte *data, const bspLump_t *lump){

	bspPlane_t	*in;
	glqPlane	*out;
	glqVec3		normal;
	int			i;

	in = (bspPlane_t *)(data + lump->offset);
	if (lump->length % sizeof(bspPlane_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numPlanes = lump->length / sizeof(bspPlane_t);
	if (cm.numPlanes < 1)
		Com_Error(false, "CM_LoadMap: no planes (%s)", cm.name);
	else if (cm.numPlanes > BSP_MAX_PLANES)
		Com_Error(false, "CM_LoadMap: too many planes (%s)", cm.name);

	cm.planes = out = (glqPlane *)Mem_Alloc(cm.numPlanes * sizeof(glqPlane), TAG_COLLISION);
	cm.size += cm.numPlanes * sizeof(glqPlane);

	for (i = 0; i < cm.numPlanes; i++, in++, out++){
		normal[0] = LittleFloat(in->normal[0]);
		normal[1] = LittleFloat(in->normal[1]);
		normal[2] = LittleFloat(in->normal[2]);

		out->Set(normal, LittleFloat(in->dist));
	}
}

/*
 ==================
 
 ==================
*/
static void CM_LoadLeafs (const byte *data, const bspLump_t *lump){

	bspLeaf_t	*in;
	clipLeaf_t	*out;
	int			i, j, p;

	in = (bspLeaf_t *)(data + lump->offset);
	if (lump->length % sizeof(bspLeaf_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numLeafs = lump->length / sizeof(bspLeaf_t);
	if (cm.numLeafs < 1)
		Com_Error(false, "CM_LoadMap: no leafs (%s)", cm.name);
	else if (cm.numLeafs > BSP_MAX_LEAFS)
		Com_Error(false, "CM_LoadMap: too many leafs (%s)", cm.name);

	cm.leafs = out = (clipLeaf_t *)Mem_Alloc(cm.numLeafs * sizeof(clipLeaf_t), TAG_COLLISION);
	cm.size += cm.numLeafs * sizeof(clipLeaf_t);

	for (i = 0; i < cm.numLeafs; i++, in++, out++){
		for (j = 0; j < 3; j++){
			out->bounds[0][j] = LittleFloat(in->mins[j]);
			out->bounds[1][j] = LittleFloat(in->maxs[j]);
		}

		p = LittleLong(in->visOfs);
		if (p == -1)
			out->compressedPVS = NULL;
		else
			out->compressedPVS = cm.pvs + p;
	}
}

/*
 ==================
 
 ==================
*/
static void CM_LoadNodes (const byte *data, const bspLump_t *lump){

	bspNode_t	*in;
	clipNode_t	*out;
	int			i;

	in = (bspNode_t *)(data + lump->offset);
	if (lump->length % sizeof(bspNode_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numNodes = lump->length / sizeof(bspNode_t);
	if (cm.numNodes < 1)
		Com_Error(false, "CM_LoadMap: no nodes (%s)", cm.name);
	else if (cm.numNodes > BSP_MAX_NODES)
		Com_Error(false, "CM_LoadMap: too many nodes (%s)", cm.name);

	cm.nodes = out = (clipNode_t *)Mem_Alloc(cm.numNodes * sizeof(clipNode_t), TAG_COLLISION);
	cm.size += cm.numNodes * sizeof(clipNode_t);

	for (i = 0; i < cm.numNodes; i++, in++, out++){

	}
}

/*
 ==================
 
 ==================
*/
static void CM_LoadClipNodes (const byte *data, const bspLump_t *lump){

	bspClipNode_t	*in;
	clipClipNode_t	*out;
	int				i;

	in = (bspClipNode_t *)(data + lump->offset);
	if (lump->length % sizeof(bspClipNode_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numClipNodes = lump->length / sizeof(bspClipNode_t);
	if (cm.numClipNodes < 1)
		Com_Error(false, "CM_LoadMap: no clip nodes (%s)", cm.name);
	else if (cm.numClipNodes > BSP_MAX_CLIP_NODES)
		Com_Error(false, "CM_LoadMap: too many clip nodes (%s)", cm.name);

	cm.clipNodes = out = (clipClipNode_t *)Mem_Alloc(cm.numClipNodes * sizeof(clipClipNode_t), TAG_COLLISION);
	cm.size += cm.numClipNodes * sizeof(clipClipNode_t);

	for (i = 0; i < cm.numClipNodes; i++, in++, out++){

	}
}

/*
 ==================
 CM_LoadInlineModels
 ==================
*/
static void CM_LoadInlineModels (const byte *data, const bspLump_t *lump){

	bspInlineModel_t	*in;
	clipInlineModel_t	*out;
	int					i, j;

	in = (bspInlineModel_t *)(data + lump->offset);
	if (lump->length % sizeof(bspInlineModel_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numInlineModels = lump->length / sizeof(bspInlineModel_t);
	if (cm.numInlineModels < 1)
		Com_Error(false, "CM_LoadMap: no inline models (%s)", cm.name);
	else if (cm.numInlineModels > BSP_MAX_INLINEMODELS)
		Com_Error(false, "CM_LoadMap: too many inline models (%s)", cm.name);

	cm.inlineModels = out = (clipInlineModel_t *)Mem_Alloc(cm.numInlineModels * sizeof(clipInlineModel_t), TAG_COLLISION);
	cm.size += cm.numInlineModels * sizeof(clipInlineModel_t);

	for (i = 0; i < cm.numInlineModels; i++, in++, out++){
		for (j = 0; j < 3; j++){
			out->bounds[0][j] = LittleFloat(in->mins[j]) - 1.0f;
			out->bounds[1][j] = LittleFloat(in->maxs[j]) + 1.0f;
		}

		// Load the inline model
		CM_LoadInlineModel(i, out);
	}
}

/*
 ==================
 
 FIXME: try out caching this later
 ==================
*/
void CM_LoadMap (const char *name){

	bspHeader_t	*header;
	byte		*data;
	int			i;

	// Check if already loaded
	if (cm.loaded){
		if (!Str_ICompare(cm.name, name))
			return;

		Com_Error(false, "CM_LoadMap: can't load '%s' because another clip map is already loaded", name);
	}

	// Load the file
	FS_ReadFile(name, (void **)&data);
	if (!data)
		Com_Error(false, "R_LoadMap: '%s' not found", name);

	// Fill it in
	cm.loaded = true;

	Str_Copy(cm.name, name, sizeof(cm.name));
	cm.size = 0;

	// Byte swap the header fields and sanity check
	header = (bspHeader_t *)data;

	for (i = 0; i < sizeof(bspHeader_t) / 4; i++)
		((int *)header)[i] = LittleLong(((int *)header)[i]);

	if (header->version != BSP_VERSION)
		Com_Error(false, "CM_LoadMap: wrong version number (%i should be %i) (%s)", header->version, BSP_VERSION, cm.name);

	// Load the lumps
	CM_LoadEntityString(data, &header->lumps[LUMP_ENTITIES]);
	CM_LoadPlanes(data, &header->lumps[LUMP_PLANES]);
	CM_LoadPVS(data, &header->lumps[LUMP_PVS]);
	CM_LoadLeafs(data, &header->lumps[LUMP_LEAFS]);
	CM_LoadNodes(data, &header->lumps[LUMP_NODES]);
	CM_LoadClipNodes(data, &header->lumps[LUMP_CLIPNODES]);
	CM_LoadInlineModels(data, &header->lumps[LUMP_INLINEMODELS]);

	// Free file data
	FS_FreeFile(data);

	// Create the box model
	CM_CreateBoxModel();
}

/*
 ==================
 
 ==================
*/
void CM_FreeMap (){

	if (!cm.loaded)
		return;

	// Clear all models
	CM_ClearModels();

	// Free all collision system allocations
	Mem_FreeAll(TAG_COLLISION, false);

	Mem_Fill(&cm, 0, sizeof(clipMap_t));
}